package me.zhengjie.webuser.util;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.io.*;
import java.nio.file.*;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

@Slf4j
@Component
public class AvatarUtils {

    @Value("${config.use-remote-service}")
    private boolean useRemoteService;

    /**
     * 远程服务存储根路径
     */
    @Value("${remote.base-path:/data/avatars}")
    private String remoteBasePath;

    /**
     * 本地存储根路径
     */
    @Value("${local.base-path:/data/avatars-local}")
    private String localBasePath;

    private static final DateTimeFormatter DATE_FORMAT =
            DateTimeFormatter.ofPattern("yyyyMMdd");

    private final GenericObjectPool<ChannelSftp> sftpPool;

    @Autowired
    public AvatarUtils(GenericObjectPool<ChannelSftp> sftpPool) {
        this.sftpPool = sftpPool;
    }

    /**
     * 上传用户头像，根据 useRemoteService 选择 SFTP 或本地存储，上传成功后清除缓存
     */
    @CacheEvict(value = "avatars", key = "#userId")
    public String uploadAvatar(MultipartFile file, Integer userId) throws IOException {
        validateFile(file);
        String original = file.getOriginalFilename();
        String ext = original == null ? "jpg" : FilenameUtils.getExtension(original).toLowerCase();
        String relativePath = buildRemotePath(userId);
        String fullPath;

        if (!useRemoteService) {
            // 本地存储
            Path dir = Paths.get(localBasePath, relativePath);
            Files.createDirectories(dir);
            String filename = generateFilename(relativePath);
            Path target = dir.resolve(filename);
            try (InputStream in = compressImage(file.getInputStream(), ext, 0.7f)) {
                Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
            }
            fullPath = target.toString();
        } else {
            // 远程 SFTP，保持原有实现
            ChannelSftp sftp = null;
            int retry = 0;
            int maxRetry = 3;
            try {
                while (retry < maxRetry) {
                    try {
                        sftp = sftpPool.borrowObject();
                        String remoteDir = remoteBasePath + "/" + relativePath;
                        try (InputStream in = compressImage(file.getInputStream(), ext, 0.7f)) {
                            return doUpload(sftp, in, remoteDir);
                        }
                    } catch (JSchException | SftpException e) {
                        retry++;
                        log.warn("SFTP上传失败，重试 {}/{}", retry, maxRetry, e);
                        if (sftp != null) {
                            try { sftpPool.invalidateObject(sftp); } catch (Exception ex) { log.error("标记失效失败", ex); }
                            sftp = null;
                        }
                        if (retry >= maxRetry) {
                            throw new IOException("SFTP上传失败，达到最大重试", e);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                throw new IOException("无法完成 SFTP 上传");
            } finally {
                if (sftp != null) {
                    sftpPool.returnObject(sftp);
                }
            }
        }
        return fullPath;
    }

    /**
     * 下载并压缩头像，结果缓存至 avatars
     */
    @Cacheable(value = "avatars", key = "#userId")
    public AvatarResource downloadAvatar(String remoteFilePath, Integer userId) throws IOException {
        String ext = FilenameUtils.getExtension(remoteFilePath);
        byte[] data;
        long lastModified;
        String filename = FilenameUtils.getName(remoteFilePath);

        if (!useRemoteService) {
            // 本地读取
            Path target = Paths.get(remoteFilePath);
            lastModified = Files.getLastModifiedTime(target).toMillis();
            try (InputStream fis = Files.newInputStream(target);
                 InputStream compressed = compressImage(fis, ext, 0.8f)) {
                data = compressed.readAllBytes();
            }
        } else {
            // 远程 SFTP，保持原有实现
            ChannelSftp sftp = null;
            boolean isValid = true;
            try {
                sftp = sftpPool.borrowObject();

                SftpATTRS attrs = sftp.stat(remoteFilePath);
                lastModified = attrs.getMTime() * 1000L;

                try (InputStream sftpIn = sftp.get(remoteFilePath);
                     InputStream compressed = compressImage(sftpIn,
                             ext, 0.8f)) {

                    data = compressed.readAllBytes();
                }
            } catch (Exception e) {
                isValid = false;
                if (sftp != null) {
                    try { sftpPool.invalidateObject(sftp); } catch (Exception ex) { log.error("标记失效失败", ex); }
                }
                throw new IOException("下载头像失败: " + remoteFilePath, e);
            } finally {
                if (sftp != null && isValid) {
                    try {
                        sftpPool.returnObject(sftp);
                    } catch (Exception ex) {
                        log.error("归还连接失败", ex);
                    }
                }
            }
        }

        String eTag = "\"" + DigestUtils.md5DigestAsHex(data) + "\"";

        return new AvatarResource(
                data,
                eTag,
                lastModified,
                filename
        );
    }

    private InputStream compressImage(InputStream in, String format, float quality) throws IOException {
        String fmt = "bmp".equalsIgnoreCase(format) ? "png" : format;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Thumbnails.of(in)
                .scale(1.0)
                .outputFormat(fmt)
                .outputQuality(quality)
                .toOutputStream(baos);
        return new ByteArrayInputStream(baos.toByteArray());
    }

    private String doUpload(ChannelSftp sftp, InputStream in, String remoteDir)
            throws SftpException {
        String filename = generateFilename(remoteDir);
        String full = remoteDir + "/" + filename;
        mkdirs(sftp, remoteDir);
        sftp.put(in, full, ChannelSftp.OVERWRITE);
        return full;
    }

    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) throw new RuntimeException("文件不能为空");
        String name = file.getOriginalFilename();
        if (name == null || !name.contains(".")) throw new RuntimeException("文件名不合法");
        String ext = FilenameUtils.getExtension(name).toLowerCase();
        if (!Arrays.asList("jpg","jpeg","png","bmp").contains(ext)) throw new RuntimeException("仅支持 JPG/PNG/BMP 格式");
        try (InputStream is = file.getInputStream()) {
            if (ImageIO.read(is) == null) throw new RuntimeException("文件内容非图片格式");
        } catch (IOException e) {
            throw new RuntimeException("读取文件失败", e);
        }
    }

    private String buildRemotePath(Integer userId) {
        Integer bucket = userId % 1000;
        return String.format("%d/%d", bucket, userId);
    }

    private String generateFilename(String path) {
        return "avatar.jpg";
    }

    private void mkdirs(ChannelSftp sftp, String path) throws SftpException {
        String[] parts = path.split("/");
        StringBuilder p = new StringBuilder();
        for (String dir : parts) {
            if (dir.isEmpty()) continue;
            p.append("/").append(dir);
            try {
                sftp.stat(p.toString());
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    sftp.mkdir(p.toString());
                }
            }
        }
    }

    public static MediaType determineMediaType(String extension) {
        if (extension == null) return MediaType.APPLICATION_OCTET_STREAM;
        switch (extension.toLowerCase()) {
            case "jpg": case "jpeg": return MediaType.IMAGE_JPEG;
            case "png": return MediaType.IMAGE_PNG;
            case "gif": return MediaType.IMAGE_GIF;
            case "bmp": return MediaType.parseMediaType("image/bmp");
            default: return MediaType.APPLICATION_OCTET_STREAM;
        }
    }
}
